package cn.silver.framework.mybatis.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.silver.framework.core.exception.ServiceException;
import cn.silver.framework.core.utils.MapstructUtils;
import cn.silver.framework.core.utils.StringUtils;
import cn.silver.framework.core.utils.reflect.ReflectUtils;
import cn.silver.framework.mybatis.annotation.TableField;
import cn.silver.framework.mybatis.bean.BaseBean;
import cn.silver.framework.mybatis.bean.PageModel;
import cn.silver.framework.mybatis.constant.PageContant;
import cn.silver.framework.mybatis.enums.SearchTypeEnums;
import cn.silver.framework.mybatis.domain.BaseEntity;
import cn.silver.framework.mybatis.mapper.BaseMapperPlus;
import cn.silver.framework.mybatis.param.BaseParam;
import cn.silver.framework.mybatis.service.IBaseService;
import cn.silver.framework.mybatis.utils.SqlUtils;
import cn.silver.framework.openapi.model.ResultPage;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
public abstract class BaseServiceImpl<M extends BaseMapperPlus<T, V>, T extends BaseEntity, P extends BaseParam, V extends BaseBean> extends ServiceImpl<M, T> implements IBaseService<T, P, V> {

    protected final Class<?>[] typeArguments = GenericTypeUtils.resolveTypeArguments(getClass(), ServiceImpl.class);

    protected Class<P> currentParamClass() {
        return (Class<P>) GenericTypeUtils.resolveTypeArguments(getClass(), BaseServiceImpl.class)[2];
    }

    protected Class<V> currentVoClass() {
        return (Class<V>) GenericTypeUtils.resolveTypeArguments(getClass(), BaseServiceImpl.class)[3];
    }

    @Override
    public ResultPage<V> selectPage(PageModel page, P filter) {
        ResultPage<V> result = new ResultPage<>();
        IPage<T> pages = this.baseMapper.selectPage(this.build(page), this.buildQueryWrapper(filter));
        result.setTotal(pages.getTotal());
        if (pages.getTotal() > 0) {
            result.setRows(MapstructUtils.convert(pages.getRecords(), currentVoClass()));
        }
        return result;
    }

    @Override
    public ResultPage<V> selectPage(PageModel page, Wrapper<T> filter) {
        ResultPage<V> result = new ResultPage<>();
        IPage<T> pages = this.baseMapper.selectPage(new Page<>(page.getPageNum(), page.getPageSize()), filter);
        result.setTotal(pages.getTotal());
        if (pages.getTotal() > 0) {
            result.setRows(MapstructUtils.convert(pages.getRecords(), currentVoClass()));
        }
        return result;
    }

    @Override
    public List<V> selectList(P entity) {
//        List<T> result = this.baseMapper.selectList(this.buildQueryWrapper(entity));
//        return CollectionUtils.isNotEmpty(result) ? MapstructUtils.convert(result, this.currentVoClass()) : Collections.emptyList();
        return this.baseMapper.selectVoList(this.buildQueryWrapper(entity), currentVoClass());
    }

    @Override
    public List<V> selectList(Wrapper<T> wrapper) {
        List<T> result = this.baseMapper.selectList(wrapper);
        return CollectionUtils.isNotEmpty(result) ? MapstructUtils.convert(result, this.currentVoClass()) : Collections.emptyList();
    }

    @Override
    public List<V> selectByIds(Collection<String> ids) {
        List<T> result = this.baseMapper.selectBatchIds(ids);
        return CollectionUtils.isNotEmpty(result) ? MapstructUtils.convert(result, this.currentVoClass()) : Collections.emptyList();
    }

    @Override
    public Long selectCount(P entity) {
        return this.baseMapper.selectCount(this.buildQueryWrapper(entity));
    }

    @Override
    public Long selectCount(Wrapper<T> wrapper) {
        return this.baseMapper.selectCount(wrapper);
    }

    @Override
    public V selectOne(P entity) {
        T result = this.baseMapper.selectOne(this.buildQueryWrapper(entity));
        return result != null ? MapstructUtils.convert(result, currentVoClass()) : null;
    }

    @Override
    public V selectOne(Wrapper<T> wrapper) {
        T result = this.baseMapper.selectOne(wrapper);
        return result != null ? MapstructUtils.convert(result, currentVoClass()) : null;
    }

    @Override
    public V selectById(String id) {
//        T result = this.baseMapper.selectById(id);
//        return result != null ? MapstructUtils.convert(result, currentVoClass()) : null;
        return this.baseMapper.selectVoById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean insert(V model) {
        T exists = StringUtils.isNotBlank(model.getId()) ? this.baseMapper.selectById(model.getId()) : null;
        T entity = MapstructUtils.convert(model, getEntityClass());
        if (exists != null) {
            return SqlHelper.retBool(this.baseMapper.updateById(entity));
        } else {
            return SqlHelper.retBool(this.baseMapper.insert(entity));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean insertBatch(Collection<V> models) {
        List<String> ids = models.stream().map(V::getId).filter(StringUtils::isNotBlank).toList();
        List<T> exists = CollUtil.isNotEmpty(ids) ? this.baseMapper.selectBatchIds(ids) : Collections.emptyList();
        List<String> existIds = exists.stream().map(T::getId).toList();
        List<T> newEntitys = models.stream().filter(item -> !existIds.contains(item.getId())).map(item -> MapstructUtils.convert(item, getEntityClass())).toList();
        List<T> updateEntitys = models.stream().filter(item -> existIds.contains(item.getId())).map(item -> MapstructUtils.convert(item, getEntityClass())).toList();
        if (CollectionUtils.isNotEmpty(newEntitys)) {
            this.baseMapper.insertBatch(newEntitys);
        }
        if (CollectionUtils.isNotEmpty(updateEntitys)) {
            this.baseMapper.insertBatch(updateEntitys);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean update(V model) {
        return SqlHelper.retBool(this.baseMapper.updateById(MapstructUtils.convert(model, getEntityClass())));
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean update(V model, UpdateWrapper<T> wrapper) {
        return SqlHelper.retBool(this.baseMapper.update(MapstructUtils.convert(model, getEntityClass()), wrapper));
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean updateBatch(Collection<V> models) {
        List<T> entities = models.stream().map(item -> MapstructUtils.convert(item, getEntityClass())).toList();
        return this.baseMapper.updateBatchById(entities);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean delete(String id) {
        return SqlHelper.retBool(this.baseMapper.deleteById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean delete(Wrapper<T> wrapper) {
        return SqlHelper.retBool(this.baseMapper.delete(wrapper));
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean deleteBatch(Collection<String> ids) {
        return SqlHelper.retBool(this.baseMapper.deleteByIds(ids));
    }

    public Page<T> build(PageModel domain) {
        Integer pageNum = ObjectUtil.defaultIfNull(domain.getPageNum(), PageContant.DEFAULT_PAGE_NUM);
        Integer pageSize = ObjectUtil.defaultIfNull(domain.getPageSize(), PageContant.DEFAULT_PAGE_SIZE);
        if (pageNum <= 0) {
            pageNum = PageContant.DEFAULT_PAGE_NUM;
        }
        Page<T> page = new Page<>(pageNum, pageSize);
        List<OrderItem> orderItems = buildOrderItem(domain);
        if (CollUtil.isNotEmpty(orderItems)) {
            page.addOrder(orderItems);
        }
        return page;
    }

    public List<OrderItem> buildOrderItem(PageModel domain) {
        if (org.apache.commons.lang3.StringUtils.isBlank(domain.getOrderByColumn()) || org.apache.commons.lang3.StringUtils.isBlank(domain.getIsAsc())) {
            return Collections.emptyList();
        }
        String orderBy = SqlUtils.escapeOrderBySql(domain.getOrderByColumn());
        orderBy = StringUtils.toUnderScoreCase(orderBy);

        // 兼容前端排序类型
        String isAsc = org.apache.commons.lang3.StringUtils.replaceEach(domain.getIsAsc(), new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
        String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
        String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
            throw new ServiceException("排序参数有误");
        }
        List<OrderItem> list = new ArrayList<>();
        // 每个字段各自排序
        for (int i = 0; i < orderByArr.length; i++) {
            String orderByStr = orderByArr[i];
            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
            if ("asc".equals(isAscStr)) {
                list.add(OrderItem.asc(orderByStr));
            } else if ("desc".equals(isAscStr)) {
                list.add(OrderItem.desc(orderByStr));
            } else {
                throw new ServiceException("排序参数有误");
            }
        }
        return list;
    }

    /**
     * 构建查询条件
     *
     * @param param 查询对象
     */
    @Override
    public QueryWrapper<T> buildQueryWrapper(P param) {
        QueryWrapper<T> lqw = new QueryWrapper<>();
        JSONObject params = param.getParams();
        List<Field> fields = Arrays.stream(param.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(TableField.class)).toList();
        for (Field field : fields) {
            TableField column = field.getAnnotation(TableField.class);
            Object value = ReflectUtils.invokeGetter(param, field.getName());
            if (column.searchType().equals(SearchTypeEnums.EQ)) {
                lqw.eq(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.NE)) {
                lqw.ne(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.GT)) {
                lqw.gt(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.GE)) {
                lqw.ge(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.LT)) {
                lqw.lt(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.LE)) {
                lqw.le(ObjectUtils.isNotEmpty(value), column.name(), value);
            } else if (column.searchType().equals(SearchTypeEnums.LIKE)) {
                lqw.like(ObjectUtils.isNotEmpty(value), column.name(), String.valueOf(value).trim());
            } else if (column.searchType().equals(SearchTypeEnums.IN)) {
                lqw.eq(ObjectUtils.isNotEmpty(value), column.name(), value);
                lqw.in(params.containsKey(field.getName()), column.name(), params.getJSONArray(column.name()).toList(field.getType()));
            } else if (column.searchType().equals(SearchTypeEnums.BETWEEN)) {
                if (params.containsKey(field.getName()) && StringUtils.isNotBlank(params.getStr(field.getName()))) {
                    String[] strings = params.getStr(field.getName()).trim().split(",");
                    lqw.between(column.name(), strings[0], strings[1]);
                }
            }
        }
        return lqw;
    }
}
